
var ContainerTest = new YAHOO.tool.TestCase({

    name: "ContainerTest",

    setUp: function () {

        // バインド先指定
        this.constractors = [
            // クラス指定
            function(binder, clazz) {
                return binder.bind( clazz || animal.Kitten );
            },
            // プロバイダ指定
            function(binder, clazz) {
                return binder.bindProvider( function() {
                    return clazz ? new clazz : new animal.Kitten();
                } );
            },
            // インスタンス指定
            function(binder, clazz) {
                return binder.bindInstance( clazz ? new clazz : new animal.Kitten() );
            }
        ];

    },
    tearDown: function () {
    },

    /**
     * シンプルなバインドのテスト
     */
    testBind: function () {
        var assert = YAHOO.util.Assert;
        for (var i=0; i<this.constractors.length; i++) {

            // オプションなし
            var thiz = this;
            var c = new container.Container( function( binder ) {
                thiz.constractors[i](binder).to( "mii" );
                thiz.constractors[i](binder).to( "tora" );
            });

            var mii = c.get("mii");
            assert.areEqual( mii.getName(), undefined ); // インジェクションされていない。
            assert.areEqual( mii.initialized, false );
            assert.areEqual( mii.destroyed,     false );

            var tora = c.get("tora");
            assert.areEqual( tora.getName(), undefined ); // インジェクションされていない。
            assert.areEqual( tora.initialized, false );
            assert.areEqual( tora.destroyed,     false );
        }
    },

    /**
     * Typeでのコンポーネント取得のテスト
     */
    testGetByType: function () {
        var assert = YAHOO.util.Assert;
        for (var i=0; i<this.constractors.length; i++) {

            // オプションなし
            var thiz = this;
            var c = new container.Container( function( binder ) {
                thiz.constractors[i](binder).inject( {name: "mii"} );
                thiz.constractors[i](binder).inject( {name: "shiro"} );
                thiz.constractors[i](binder, animal.Tiger ).inject( {name: "tora"} );
                thiz.constractors[i](binder, animal.Tiger ).inject( {name: "kuro"} );
            });

            // initを持つ最初に登録されたコンポーネント。
            var mii = c.get( container.types.has( "init" ) );
            assert.areEqual( mii.getName(), "mii" );

            // initを持たない最初に登録されたコンポーネント。
            // 存在しない
            try {
                c.get( container.types.not( "init" ) );
                fail();
            } catch ( error ) {}

            // biteを持つ最初に登録されたコンポーネント。
            kitten = c.get( container.types.has( /bite/ ) );
            assert.areEqual( kitten.getName(), "tora" );

            // biteまたはmeowを持つ最初に登録されたコンポーネント。
            kitten = c.get( container.types.hasAny( /bite/, /meow/ ) );
            assert.areEqual( kitten.getName(), "mii" );

            // initを持ちmeowを持たない最初に登録されたコンポーネント。
            kitten = c.get( container.types.has(
                container.types.has("init"),
                container.types.not( container.types.has(/meow/)) ));
            assert.areEqual( kitten.getName(), "tora" );


            // initを持つコンポーネント。
            var list = c.gets( container.types.has( "init" ) );
            assert.areEqual( list.length, 4 );
            assert.areEqual( list[0].getName(), "mii" );
            assert.areEqual( list[1].getName(), "shiro" );
            assert.areEqual( list[2].getName(), "tora" );
            assert.areEqual( list[3].getName(), "kuro" );

            // initを持たないコンポーネント。
            // 存在しない
            list = c.gets( container.types.not( "init" ) );
            assert.areEqual( list.length, 0 );

            // biteを持つコンポーネント。
            list = c.gets( container.types.has( /bite/ ) );
            assert.areEqual( list.length, 2 );
            assert.areEqual( list[0].getName(), "tora" );
            assert.areEqual( list[1].getName(), "kuro" );

            // biteまたはmeowを持つ最初に登録されたコンポーネント。
            list = c.gets( container.types.hasAny( /bite/, /meow/ ) );
            assert.areEqual( list.length, 4 );
            assert.areEqual( list[0].getName(), "mii" );
            assert.areEqual( list[1].getName(), "shiro" );
            assert.areEqual( list[2].getName(), "tora" );
            assert.areEqual( list[3].getName(), "kuro" );

            // initを持ちmeowを持たない最初に登録されたコンポーネント。
            list = c.gets( container.types.has(
                container.types.has("init"),
                container.types.not( container.types.has(/meow/)) ));
            assert.areEqual( list.length, 2 );
            assert.areEqual( list[0].getName(), "tora" );
            assert.areEqual( list[1].getName(), "kuro" );
        }
    },

    /**
     * 自動インジェクションのテスト
     */
    testAutoInjection: function () {
        var assert = YAHOO.util.Assert;

            var thiz = this;
            var c = new container.Container( function( binder ) {
                
                var TestA = function() {
                    this.a = container.Inject;
                    this.as = container.Injects;
                    this.b = container.Inject( "test" );
                    this.bs = container.Injects( "test" );
                    this.c = container.Inject;
                };
                binder.bind( TestA ).to( "obj" )
                    .inject( { c:"test" } ); // プロパティインジェクションが指定された場合はそちらが優先
                
                // 自動インジェクションしない設定がされている場合、インジェクションは機能しない。
                var TestB = function() {
                    this.a = container.Inject;
                    this.as = container.Injects;
                    this.b = container.Inject( "test" );
                    this.bs = container.Injects( "test" );
                };
                TestB.prototype.meta = { 
                    "@Container": { "@AutoInjection" : false }
                };
                binder.bind( TestB ).to( "obj2" ).inject( { b:"test", bs:"test2" } );
                
                binder.bindInstance( "aaa" ).to( "a" );
                binder.bindInstance( "bbb" ).to( "a" );
                
                binder.bindInstance( "aaa" ).to( "as" );
                binder.bindInstance( "bbb" ).to( "as" );
                binder.bindInstance( "ccc" ).to( "as" );
                
                binder.bindInstance( "xxx" ).to( "test" );
                binder.bindInstance( "yyy" ).to( "test" );
                
                binder.bindInstance( "ccc" ).to( "c" );
            });

            var obj = c.get("obj");
            assert.areEqual( obj.a, "aaa" );
            assert.areEqual( obj.as.length, 3 );
            assert.areEqual( obj.as[0], "aaa" );
            assert.areEqual( obj.as[1], "bbb" );
            assert.areEqual( obj.as[2], "ccc" );
            assert.areEqual( obj.b, "xxx" );
            assert.areEqual( obj.bs.length, 2 );
            assert.areEqual( obj.bs[0], "xxx" );
            assert.areEqual( obj.bs[1], "yyy" );
            assert.areEqual( obj.c, "test" );
            
            var obj2 = c.get("obj2");
            assert.areEqual( obj2.a, container.Inject ); // インジェクションされない
            assert.areEqual( obj2.as, container.Injects );
            assert.areEqual( obj2.b, "test" );
            assert.areEqual( obj2.bs, "test2" );
    },

    /**
     * Typeでのコンポーネント取得のテスト
     */
    testAutoInjectionByType: function () {
        var assert = YAHOO.util.Assert;
        
        var thiz = this;
        var c = new container.Container( function( binder ) {
            
            var TestA = function() {
                this.a = container.Inject( container.types.has( "bite" ) );
                this.b = container.Injects( container.types.has( "meow" ) );
                this.c = container.Injects( container.types.has( "init" ) );
            };
            binder.bind( TestA ).to( "obj" );
            
            binder.bind( animal.Kitten ).to( "mii" ).inject( {name:"mii"} );
            binder.bind( animal.Kitten ).to( "mike" ).inject( {name:"mike"} );
            binder.bind( animal.Tiger ).to( "tora" ).inject( {name:"tora"} );
            binder.bind( animal.Tiger ).to( "shiro" ).inject( {name:"shiro"} );
        });
        var obj = c.get( "obj" );
        assert.areEqual( obj.a.name, "tora" );
        assert.areEqual( obj.b.length, 2 );
        assert.areEqual( obj.b[0].name, "mii" );
        assert.areEqual( obj.b[1].name, "mike" );
        assert.areEqual( obj.c.length, 4 );
        assert.areEqual( obj.c[0].name, "mii" );
        assert.areEqual( obj.c[1].name, "mike" );
        assert.areEqual( obj.c[2].name, "tora" );
        assert.areEqual( obj.c[3].name, "shiro" );
    },
    
    /**
     * 初期化メソッド、インジェクション指定のテスト
     */
    testInit_and_Inject: function () {

        var assert = YAHOO.util.Assert;
        for (var i=0; i<this.constractors.length; i++) {

            // 初期化メソッド/インジェクション指定を追加
            var thiz = this;
            var c = new container.Container( function( binder ) {
                thiz.constractors[i]( binder).to( "mii" ).inject( {
                   "name":  "mii"
                }).initialize( function( obj, container ) {
                    assert.areEqual( container, c );
                    obj.init();
                });

                // 別コンポーネントをインジェクション
                binder.bind( animal.Kitten ).to( "tora" ).inject( {
                    name: "tora",
                    "mii": container.component( "mii" )
                } );

                // 別コンポーネントの配列をインジェクション
                binder.bind( animal.Kitten ).to( "shiro" ).inject( {
                    name: "shiro",
                    "names": container.components( "names" )
                } );
                binder.bindInstance("a").to( "names" );
                binder.bindInstance("b").to( "names" );
                binder.bindInstance("c").to( "names" );

                // 関数の戻り値をインジェクション
                binder.bind( animal.Kitten ).to( "kuro" ).inject( {
                    name: container.provides( function( obj, container ) {
                        assert.isTrue( obj instanceof animal.Kitten );
                        assert.areSame( container, c );
                    	return "kuro";
                    } )
                } );

                // Typeで探索した値をインジェクション
                binder.bind( animal.Tiger ).to( "mike" ).inject( {
                    name: "mike",
                    kitten:  container.component( container.types.has("init") ),
                    kittens: container.components( container.types.has("init") )
                } );
                binder.bind( animal.Tiger ).inject({name: "a"});
                binder.bind( animal.Tiger ).inject({name: "b"});

                // Typeで探索した値をインジェクションその2
                // ↑と循環参照(相互持ち合い)する。また、自身をフィールドとして持つ。
                binder.bind( animal.Tiger ).inject( {
                    name: "tama",
                    kittens: container.components( container.types.has("init") )
                } );
            });

            var mii = c.get("mii");
            assert.areEqual( mii.getName(), "mii" );
            assert.areEqual( mii.initialized, true );
            assert.areEqual( mii.destroyed,     false );

            var tora = c.get("tora");
            assert.areEqual( tora.getName(), "tora" );
            assert.areSame( tora.mii, mii );

            var shiro = c.get("shiro");
            assert.areEqual( shiro.getName(), "shiro" );
            assert.areEqual( shiro.names.length, 3 );
            assert.areEqual( shiro.names[0], "a" );
            assert.areEqual( shiro.names[1], "b" );
            assert.areEqual( shiro.names[2], "c" );

            var kuro = c.get("kuro");
            assert.areEqual( kuro.getName(), "kuro" );

            var mike = c.get( "mike" );
            assert.areEqual( mike.getName(), "mike" );
            assert.areEqual( mike.kitten, mii );
            assert.areEqual( mike.kittens.length, 8 );
            assert.areEqual( mike.kittens[0].name, "mii" );
            assert.areEqual( mike.kittens[1].name, "tora" );
            assert.areEqual( mike.kittens[2].name, "shiro" );
            assert.areEqual( mike.kittens[3].name, "kuro" );
            assert.areEqual( mike.kittens[4].name, "mike" );
            assert.areEqual( mike.kittens[5].name, "a" );
            assert.areEqual( mike.kittens[6].name, "b" );
            assert.areEqual( mike.kittens[7].name, "tama" );

            var tama = mike.kittens[7];
            assert.areEqual( tama.getName(), "tama" );
            assert.areEqual( tama.kittens.length, 8 );
            assert.areEqual( tama.kittens[0].name, "mii" );
            assert.areEqual( tama.kittens[1].name, "tora" );
            assert.areEqual( tama.kittens[2].name, "shiro" );
            assert.areEqual( tama.kittens[3].name, "kuro" );
            assert.areEqual( tama.kittens[4].name, "mike" );
            assert.areEqual( tama.kittens[5].name, "a" );
            assert.areEqual( tama.kittens[6].name, "b" );
            assert.areEqual( tama.kittens[7].name, "tama" );

            mike = mike.kittens[4];
            assert.areEqual( mike.getName(), "mike" );
            assert.areEqual( mike.kitten, mii );
            assert.areEqual( mike.kittens.length, 8 );
            assert.areEqual( mike.kittens[0].name, "mii" );
            assert.areEqual( mike.kittens[1].name, "tora" );
            assert.areEqual( mike.kittens[2].name, "shiro" );
            assert.areEqual( mike.kittens[3].name, "kuro" );
            assert.areEqual( mike.kittens[4].name, "mike" );
            assert.areEqual( mike.kittens[5].name, "a" );
            assert.areEqual( mike.kittens[6].name, "b" );
            assert.areEqual( mike.kittens[7].name, "tama" );

            tama = tama.kittens[7];
            assert.areEqual( tama.getName(), "tama" );
            assert.areEqual( tama.kittens.length, 8 );
            assert.areEqual( tama.kittens[0].name, "mii" );
            assert.areEqual( tama.kittens[1].name, "tora" );
            assert.areEqual( tama.kittens[2].name, "shiro" );
            assert.areEqual( tama.kittens[3].name, "kuro" );
            assert.areEqual( tama.kittens[4].name, "mike" );
            assert.areEqual( tama.kittens[5].name, "a" );
            assert.areEqual( tama.kittens[6].name, "b" );
            assert.areEqual( tama.kittens[7].name, "tama" );

            mike = tama.kittens[4];
            assert.areEqual( mike.getName(), "mike" );
            assert.areEqual( mike.kitten, mii );
            assert.areEqual( mike.kittens.length, 8 );
            assert.areEqual( mike.kittens[0].name, "mii" );
            assert.areEqual( mike.kittens[1].name, "tora" );
            assert.areEqual( mike.kittens[2].name, "shiro" );
            assert.areEqual( mike.kittens[3].name, "kuro" );
            assert.areEqual( mike.kittens[4].name, "mike" );
            assert.areEqual( mike.kittens[5].name, "a" );
            assert.areEqual( mike.kittens[6].name, "b" );
            assert.areEqual( mike.kittens[7].name, "tama" );

            // Typeで取り出し。
            var mike = c.get( container.types.has("bite") );
            assert.areEqual( mike.getName(), "mike" );
            assert.areEqual( mike.kitten, mii );
            assert.areEqual( mike.kittens.length, 8 );
            assert.areEqual( mike.kittens[0].name, "mii" );
            assert.areEqual( mike.kittens[1].name, "tora" );
            assert.areEqual( mike.kittens[2].name, "shiro" );
            assert.areEqual( mike.kittens[3].name, "kuro" );
            assert.areEqual( mike.kittens[4].name, "mike" );
            assert.areEqual( mike.kittens[5].name, "a" );
            assert.areEqual( mike.kittens[6].name, "b" );
            assert.areEqual( mike.kittens[7].name, "tama" );
        }
    },

    /**
     * スコープ指定のテスト
     */
    testScope: function () {

        var assert = YAHOO.util.Assert;
        for (var i=0; i<this.constractors.length; i++) {

            var thiz = this;
            var initialized = {}; // 初期化のタイミングをチェックするための変数。初期化したらtrueを挿入
            var c = new container.Container( function( binder ) {

                // デフォルト / Singleton扱い
                thiz.constractors[i]( binder).to( "default" ).inject( {
                   "name":  "mii"
                }).initialize( function() { initialized["default"] = true; } );

                // singleton
                thiz.constractors[i]( binder).to( "singleton" ).inject( {
                   "name": "tora"
                }).scope( container.Scope.Singleton ).initialize( function() { initialized["singleton"] = true; } );

                // prototype
                thiz.constractors[i]( binder).to( "prototype" ).inject( {
                   "name": "shiro"
                }).scope( container.Scope.Prototype ).initialize( function() { initialized["prototype"] = true; } );

                // eager singleton
                thiz.constractors[i]( binder).to( "eager-singleton" ).inject( {
                   "name": "kuro"
                }).scope( container.Scope.EagerSingleton ).initialize( function() { initialized["eager-singleton"] = true; } );
            });

            // 初期化の確認 / eager-singletonのみすでに作成されている。
            assert.areEqual( initialized["default"], undefined );
            assert.areEqual( initialized["singleton"], undefined );
            assert.areEqual( initialized["prototype"], undefined );
            assert.areEqual( initialized["eager-singleton"], true );

            /// デフォルト
            var kitten = c.get("default");
            assert.areEqual( kitten.getName(),   "mii" );
            assert.areEqual( kitten.initialized, false );
            assert.areEqual( kitten.destroyed,    false );
            assert.areEqual( initialized["default"], true ); // 初期化されていることを確認。

            // パラメータを編集
            kitten.init();
            kitten.name = "edit";
            assert.areEqual( kitten.getName(),   "edit" );
            assert.areEqual( kitten.initialized, true );
            assert.areEqual( kitten.destroyed,    false );

            // 再取得 / Singletonなので変更が反映されている。
            var nkitten = c.get("default");
            assert.areEqual( nkitten, kitten );
            assert.areEqual( nkitten.getName(),   "edit" );
            assert.areEqual( nkitten.initialized, true );
            assert.areEqual( nkitten.destroyed,    false );


            /// Singleton
            kitten = c.get("singleton");
            assert.areEqual( kitten.getName(),   "tora" );
            assert.areEqual( kitten.initialized, false );
            assert.areEqual( kitten.destroyed,    false );
            assert.areEqual( initialized["singleton"], true ); // 初期化されていることを確認。

            // パラメータを編集
            kitten.init();
            kitten.name = "edit-tora";
            assert.areEqual( kitten.getName(),   "edit-tora" );
            assert.areEqual( kitten.initialized, true );
            assert.areEqual( kitten.destroyed,    false );

            // 再取得 / Singletonなので変更が反映されている。
            nkitten = c.get("singleton");
            assert.areEqual( nkitten, kitten );
            assert.areEqual( nkitten.getName(),   "edit-tora" );
            assert.areEqual( nkitten.initialized, true );
            assert.areEqual( nkitten.destroyed,    false );


            /// Prototype
            kitten = c.get("prototype");
            assert.areEqual( kitten.getName(),   "shiro" );
            assert.areEqual( kitten.initialized, false );
            assert.areEqual( kitten.destroyed,    false );
            assert.areEqual( initialized["prototype"], true ); // 初期化されていることを確認。

            // パラメータを編集
            kitten.init();
            kitten.name = "edit-shiro";
            assert.areEqual( kitten.getName(),   "edit-shiro" );
            assert.areEqual( kitten.initialized, true );
            assert.areEqual( kitten.destroyed,    false );

            // 再取得 / Prototypeなので別オブジェクト。変更も反映されない(インスタンス指定の場合以外)
            nkitten = c.get("prototype");
            if ( i != 2 ) {
                assert.areNotSame( nkitten, kitten );
                assert.areEqual( nkitten.getName(),   "shiro" );
                assert.areEqual( nkitten.initialized, false );
                assert.areEqual( nkitten.destroyed,    false );
            } else {
                // インスタンス指定の場合は実質的シングルトンなので変更が反映される。
                assert.areSame( nkitten, kitten );
                assert.areEqual( nkitten.getName(),   "shiro" ); // プロパティは再設定される。
                assert.areEqual( nkitten.initialized, true );
                assert.areEqual( nkitten.destroyed,    false );
            }


            /// Eager Singleton
            kitten = c.get("eager-singleton");
            assert.areEqual( kitten.getName(),   "kuro" );
            assert.areEqual( kitten.initialized, false );
            assert.areEqual( kitten.destroyed,    false );
            assert.areEqual( initialized["eager-singleton"], true ); // 初期化されていることを確認。

            // パラメータを編集
            kitten.init();
            kitten.name = "edit-kuro";
            assert.areEqual( kitten.getName(),   "edit-kuro" );
            assert.areEqual( kitten.initialized, true );
            assert.areEqual( kitten.destroyed,    false );

            // 再取得 / Singletonなので変更が反映されている。
            nkitten = c.get("eager-singleton");
            assert.areEqual( nkitten, kitten );
            assert.areEqual( nkitten.getName(),   "edit-kuro" );
            assert.areEqual( nkitten.initialized, true );
            assert.areEqual( nkitten.destroyed,    false );
        }
    },

    /**
     * インターセプタ指定のテスト
     */
    testInterceptor: function () {

        var assert = YAHOO.util.Assert;
        for (var i=0; i<this.constractors.length; i++) {

            var thiz = this;
            var c = new container.Container( function( binder ) {
                thiz.constractors[i]( binder).to( "mii" ).inject( {
                   "name":  "mii"
                }).intercept( function( mi ) {
                   assert.isNotNull( mi.getThis() );
                   assert.isNotNull( mi.getArguments() );
                   assert.isNotNull( mi.getMethodName() );
                   assert.isNotNull( mi.getOriginalMethod() );
                   assert.isNotNull( mi.getArguments() );

                   var res = mi.proceed();
                   mi.getThis().name += "+i";
                   return res;
                }, new container.Matcher( /.*/ ) );
            });
            var kitten = c.get("mii");
            assert.areEqual( kitten.getName(),   "mii" ); // インターセプタが実行される。
            assert.areEqual( kitten.initialized, false );
            assert.areEqual( kitten.destroyed,    false );

            kitten.init(); // インターセプタが実行される。
            assert.areEqual( kitten.getName(),   "mii+i+i" ); // インターセプタが実行される。
            assert.areEqual( kitten.initialized, true );
            assert.areEqual( kitten.destroyed,    false );

            kitten.setName( "tora" ); // インターセプタが実行される。
            assert.areEqual( kitten.getName(),   "tora+i" ); // インターセプタが実行される。
            assert.areEqual( kitten.initialized, true );
            assert.areEqual( kitten.destroyed,    false );

            assert.areEqual( kitten.getName(),   "tora+i+i" );
        }
    },
    /**
     * インターセプタ適用のテスト
     */
    testApplyInterceptor: function() {

        var assert = YAHOO.util.Assert;

        // テスト用クラス
        var Foo = function(){};
        Foo.prototype = {
            get: function( ){ return this.a; },
            set: function( a,b,c ){
                this.a = a;
                this.b = b;
                this.c = c;
            }
        }
        // Fooのサブクラス
        var Hoge = function(){}
        Hoge.prototype = new Foo();
        Hoge.prototype.getHoge = function(){ return this.b; }


        // MethodInvocationに値が入っていることのテスト。
        var c = new container.Container( function( binder ) {
            binder.bind( Foo ).to( "foo" ).intercept( function( mi ){
                assert.areEqual( mi.getMethodName(), "set" ); // getの実行時に実行されるとエラーになる。
                assert.areEqual( mi.getThis(), foo );
                assert.areEqual( mi.getArguments()[0], "x" );
                assert.areEqual( mi.getArguments()[1], "y" );
                assert.areEqual( mi.getArguments()[2], "z" );
                assert.isNotNull( mi.getOriginalMethod() );
                return mi.proceed();
            }, container.any() );
        });

        var foo = c.get("foo");
        foo.set("x","y","z");
        // オリジナルのsetが実行されている。
        assert.areEqual( foo.a, "x" );
        assert.areEqual( foo.b, "y" );
        assert.areEqual( foo.c, "z" );


        // マッチ条件を指定し、特定のメソッドにのみインターセプタが設定されることを確認。
        c = new container.Container( function( binder ) {
            binder.bind( Foo ).to( "foo" ).intercept( function( mi ){
                if (!mi.getThis().called) { mi.getThis().called = ""  }
                mi.getThis().called += mi.getMethodName();
                return mi.proceed();
            }, new container.Matcher( /set/ ) );
        });
        foo = c.get("foo");
        foo.get();
        foo.set("x","y","z");
        assert.areEqual( foo.called, "set" ); // setのみ呼び出されている。


        // 派生クラスを指定し、親クラスのAPIにもインターセプタが指定されていることを確認。
        c = new container.Container( function( binder ) {
            binder.bind( Hoge ).to( "hoge" ).intercept( function( mi ){
                if (!mi.getThis().called) { mi.getThis().called = ""  }
                mi.getThis().called += "_" + mi.getMethodName();
                return mi.proceed();
            }, new container.Matcher( /get.*/ ) );

            binder.bind( Hoge ).to( "hoge2" );
        });
        hoge = c.get("hoge");
        hoge.set("x","y","z");
        assert.areEqual( hoge.get(), "x");
        assert.areEqual( hoge.getHoge(), "y");
        assert.areEqual( hoge.called, "_get_getHoge" ); // getHogeにも適用されている。

        hoge2 = c.get("hoge2");
        hoge2.set("x","y","z");
        assert.areEqual( hoge2.get(), "x");
        assert.areEqual( hoge2.getHoge(), "y");
        assert.areEqual( hoge2.called, undefined ); // 別のHogeには影響しない。


        // 例外
        var ThrowError = function(){};
        ThrowError.prototype = {
            throwError: function( ){ throw new Error(); }
        }
        c = new container.Container( function( binder ) {
            binder.bind( ThrowError ).to( "throwError" ).intercept( function( mi ){
                try {
                    mi.getThis().called = mi.getMethodName();
                    mi.proceed();
                    fail();
                } catch ( arg ) {
                    assert.isNotNull( arg );
                    throw arg;
                }
            }, container.any() );
        });

        var te = c.get("throwError");
        try {
            te.throwError();
            fail();
        } catch ( error ) {
            assert.isNotNull( error );
        }
        assert.areEqual( te.called, "throwError" );


        // 2重適用
        c = new container.Container( function( binder ) {

            // インターセプタ
            var interceptor = function( mi ) {
                if (!mi.getThis().called) { mi.getThis().called = ""  }
                mi.getThis().called += "_" + mi.getMethodName();
                return mi.proceed();
            };

            // 2重にインターセプタを適用
            var builder = binder.bind( Hoge ).to( "hoge" );
            builder.intercept( interceptor, new container.Matcher( /get.*/ ) );
            builder.intercept( interceptor, new container.Matcher( /.*/ ) );

            binder.bind( Hoge ).to( "hoge2" );
        });
        hoge = c.get("hoge");
        hoge.set("x","y","z");
        assert.areEqual( hoge.get(), "x");
        assert.areEqual( hoge.getHoge(), "y");
        assert.areEqual( hoge.called, "_set_get_get_getHoge_getHoge" ); // setに1つ、get,getHogeに2つ適用されている。

        hoge2 = c.get("hoge2");
        hoge2.set("x","y","z");
        assert.areEqual( hoge2.get(), "x");
        assert.areEqual( hoge2.getHoge(), "y");
        assert.areEqual( hoge2.called, undefined ); // 別のHogeには影響しない。
    },

    /**
     * メソッドバインドのテスト。
     */
    testBindMethod: function() {

        var assert = YAHOO.util.Assert;
        var c = new container.Container( function( binder ){
            binder.bind( animal.Kitten ).to( "mii" ).inject( { 
                name: "mii",
                child: container.component( container.types.has("bite") )
            } );
            binder.bind( animal.Kitten ).to( "mii" ).inject( { name: "kuro"} );

            binder.bind( animal.Tiger ).inject( { 
                name: "tora",
                child: container.component( container.types.has("bite") )
            } );
            binder.bind( animal.Tiger ).inject( { 
                name: "shiro",
                child: container.component( container.types.has("bite") )
            } );

            binder.bindMethod( "mii", "init" ).to("init" );
            binder.bindMethod( "mii", "getName").to( "getName" );
            binder.bindMethod( "mii", "setName" ).to( "setName" );
            
            // メソッドの配列をバインド
            binder.bindMethods( "mii", "getName" ).to("getNames" );
            binder.bindMethods( "mii", "setName" ).to("setNames" );
            
            // typeでバインド
            binder.bindMethod( container.types.has("bite"), "getName").to( "t.getName" );
            binder.bindMethods( container.types.has("bite"), "getName").to( "t.getNames" );
        } );
        var kitten = c.get("mii");
        assert.areEqual( kitten.getName(), "mii");
        assert.areEqual( kitten.initialized, false);

        var init = c.get("init"); // 関数を取得
        init(); // 実行
        assert.areEqual( kitten.getName(), "mii");
        assert.areEqual( kitten.initialized, true);

        // 戻り値あり
        var get = c.get("getName");
        assert.areEqual( get(), "mii");

        // 引数あり
        var set = c.get("setName");
        set( "foo" );
        assert.areEqual( get(), "foo");


        var getNames = c.get("getNames");
        assert.areEqual( getNames.length, 2);
        assert.areEqual( getNames[0](), "foo");
        assert.areEqual( getNames[1](), "kuro");

        var setNames = c.get("setNames");
        assert.areEqual( setNames.length, 2);
        setNames[0]( "hoge" );
        setNames[1]( "var" );
        assert.areEqual( getNames[0](), "hoge");
        assert.areEqual( getNames[1](), "var");

        assert.areEqual( kitten.getName(), "hoge");
        
        // タイプでバインド
        assert.areEqual( c.get("t.getName")(), "tora");
        getNames = c.get("t.getNames");
        assert.areEqual( getNames.length, 2);
        assert.areEqual( getNames[0](), "tora");
        assert.areEqual( getNames[1](), "shiro");
    },

    /**
     * メソッドバインドのテスト。異常系
     */
    testBindMethod_Error: function() {

        // 存在しないコンポーネントのメソッドをバインド
        var assert = YAHOO.util.Assert;
        var c = new container.Container( function( binder ){
            binder.bindMethod( "not found", "init" ).to( "init" );
            binder.bindMethods( "not found", "init" ).to( "init2" );
        } );
        try {
           c.get("init");
           assert.fail();
        } catch( e ) {
           assert.areEqual( e.errorCode, container.ErrorCode.ComponentNotFound );
        }

        var list = c.get("init2");
        assert.areEqual( list.length, 0);
        
        
        // 循環参照。プロバイダ指定とバインドメソッドでのみ起こる。
        c = new container.Container( function( binder ){
            binder.bindMethod( "init", "foo" ).to( "init" );
            
            binder.bind( animal.Tiger ).inject({ bite: container.component("bite") });
            binder.bindMethod( container.types.has("bite"), "bite" ).to( "bite" );
            
        } );
        try {
           c.get("init");
           assert.fail();
        } catch( e ) {
           assert.areEqual( e.errorCode, container.ErrorCode.CircularReference );
        }
        try {
           c.get("bite");
           assert.fail();
        } catch( e ) {
           assert.areEqual( e.errorCode, container.ErrorCode.CircularReference );
        }
    },

   /**
     * グローバルインターセプターのテスト。
     */
    testGlobalInterceptor: function() {

        function Foo() {}
        Foo.prototype = {
            getX: function() { return this.x; },
            setX: function(x) { this.x = x; }
        }
        function interceptor( mi ) {
            if (!mi.getThis().called) { mi.getThis().called = ""  }
            mi.getThis().called += "_" + mi.getMethodName();
            return mi.proceed();
        }

        var assert = YAHOO.util.Assert;
        var c = new container.Container( function( binder ){
            binder.bind( animal.Kitten ).to( "mii" ).inject( { name: "mii"} ).intercept( interceptor, container.any() );
            binder.bindInstance( new animal.Kitten()).to("tora" ).inject( { name: "tora"} );
            binder.bindProvider( function() {
                return new animal.Kitten();
            }).to("shiro").inject( { name: "shiro"} ).intercept( interceptor, new container.Matcher( /get.*/ )) ;

            binder.bind( Foo ).to( "foo" ).inject( { x: "x"} );

            // a / すべてのコンポーネントのすべてのメソッド
            binder.bindInterceptor( function ( mi ) {
                if (!mi.getThis().called) { mi.getThis().called = ""  }
                mi.getThis().called += "_a-" + mi.getMethodName();
                return mi.proceed();
            }, container.any(), container.any()  );
            // b / すべてのコンポーネントのget*メソッド
            binder.bindInterceptor( function ( mi ) {
                if (!mi.getThis().called) { mi.getThis().called = ""  }
                mi.getThis().called += "_b-" + mi.getMethodName();
                return mi.proceed();
            }, container.any(), new container.Matcher( /get.*/ ) );
            // c / foo, shiroコンポーネントのset*メソッド
            binder.bindInterceptor( function ( mi ) {
                if (!mi.getThis().called) { mi.getThis().called = ""  }
                mi.getThis().called += "_c-" + mi.getMethodName();
                return mi.proceed();
            },  new container.Matcher( /.*o$/ ),  new container.Matcher( /set.*/ ));
            // b / Typeでバインド / foo, shiroコンポーネントのset*メソッド
            binder.bindInterceptor( function ( mi ) {
                if (!mi.getThis().called) { mi.getThis().called = ""  }
                mi.getThis().called += "_d-" + mi.getMethodName();
                return mi.proceed();
            },  container.types.has(/setX/), new container.Matcher( /set.*/ ));
            binder.bindMethod( "mii", "getName" ).to( "get" );
        } );

        // mii
        var kitten = c.get( "mii" );
        assert.areEqual(  kitten.called, undefined );
        assert.areEqual(  kitten.getName(), "mii" );
        assert.areEqual(  kitten.called, "_b-getName_a-getName_getName" );
        kitten.called = "";
        kitten.setName( "hoge" );
        assert.areEqual(  kitten.called, "_a-setName_setName" );
        assert.areEqual(  kitten.getName(), "hoge" );
        kitten.called = "";
        kitten.init();
        assert.areEqual(  kitten.initialized, true );
        assert.areEqual(  kitten.called, "_a-init_init" );

        // tora
        var kitten = c.get( "tora" );
        assert.areEqual(  kitten.called, undefined );
        assert.areEqual(  kitten.getName(), "tora" );
        assert.areEqual(  kitten.called, "_b-getName_a-getName" );
        kitten.called = "";
        kitten.setName( "hoge" );
        assert.areEqual(  kitten.called, "_a-setName" );
        assert.areEqual(  kitten.getName(), "hoge" );
        kitten.called = "";
        kitten.init();
        assert.areEqual(  kitten.initialized, true );
        assert.areEqual(  kitten.called, "_a-init" );

        // shiro
        var kitten = c.get( "shiro" );
        assert.areEqual(  kitten.called, undefined );
        assert.areEqual(  kitten.getName(), "shiro" );
        YAHOO.log( "b" );
        assert.areEqual(  kitten.called, "_b-getName_a-getName_getName");
        kitten.called = "";
        kitten.setName( "hoge" );
        assert.areEqual(  kitten.called, "_c-setName_a-setName" );
        assert.areEqual(  kitten.getName(), "hoge" );
        kitten.called = "";
        kitten.init();
        assert.areEqual(  kitten.initialized, true );
        assert.areEqual(  kitten.called, "_a-init" );

        // foo
        var foo = c.get("foo");
        assert.areEqual(  foo.called, undefined );
        assert.areEqual(  foo.getX(), "x" );
        assert.areEqual(  foo.called, "_b-getX_a-getX" );
        foo.called = "";
        foo.setX( "xx" );
        assert.areEqual(  foo.called, "_d-setX_c-setX_a-setX" );
        assert.areEqual(  foo.getX(), "xx" );

        // バインドメソッドにもインターセプタが適用される。
        var get = c.get( "get" );
        kitten = c.get( "mii" );
        kitten.setName( "mii" );
        kitten.called = "";
        assert.areEqual(  kitten.called, "" );
        assert.areEqual(  get(), "mii" );
        assert.areEqual(  kitten.called, "_b-getName_a-getName_getName" );

        kitten.called = "";
        assert.areEqual( get.apply( undefined ), "mii");
        assert.areEqual(  kitten.called, "_b-getName_a-getName_getName" );
        assert.areEqual( get.called, undefined );
    },

   /**
     * アノテーションのテスト。
     */
    testAnnotation: function() {
        var assert = YAHOO.util.Assert;
        function interceptor( mi ) {
            if (!mi.getThis().called) { mi.getThis().called = ""  }
            mi.getThis().called += "_" + mi.getMethodName();
            return mi.proceed();
        }
        function Foo() {}
        Foo.prototype = {
            getX: function() { return this.x; },
            setX: function(x) { this.x = x; }
        }
        Foo.prototype.meta = {
            "@Container": {
                "@Name": "a",
                "@Inject":{
                    "name" : "foo",
                    "x": "x"
                },
                "@Initialize": function( obj, container ) {
                    assert.isNotNull( container );
                    obj.init = true;
                },
                "@Destroy": function( obj, container ) {
                    assert.isNotNull( container );
                    obj.destroy = true;
                },
                "@Scope": container.Scope.Singleton,
                "@Intercept": [
                    [ interceptor, container.any() ],
                    [ interceptor, new container.Matcher( /get.*/ ) ]
                ]
            }
        };
        var c = new container.Container( function( binder ) {
            binder.bind( Foo );

            // モジュールで指定した場合、そちらが優先。
            // インターセプタのみ追加の動作となる。
            binder.bind( Foo ).to( "b" ).inject({
               "x":"y"
            }).initialize( function(){} ).destroy( function(obj){
                obj.destroy = false;
            }).intercept( interceptor, container.any() );

            binder.bind( Foo ).to( "c" ).scope( container.Scope.Prototype );
        } );
        var foo = c.get( "a" );
        assert.areEqual( foo.called, undefined );
        assert.areEqual( foo.name, "foo" );
        assert.areEqual( foo.init, true );
        assert.areEqual( foo.destroy, undefined  );
        assert.areEqual( foo.getX(), "x" );
        assert.areEqual( foo.called, "_getX_getX" );
        foo.called = "";
        foo.setX("y");
        assert.areEqual( foo.called, "_setX" );
        assert.areEqual( foo.getX(), "y" );
        assert.areSame( foo, c.get("a") );
        assert.areEqual( foo.getX(), c.get("a").getX() );

        c.destroy();
        assert.areEqual( foo.destroy, true  );

        var foo = c.get( "b" );
        assert.areEqual( foo.called, undefined );
        assert.areEqual( foo.name, undefined );
        assert.areEqual( foo.init, undefined );
        assert.areEqual( foo.destroy, undefined  );
        assert.areEqual( foo.getX(), "y" );
        assert.areEqual( foo.called, "_getX_getX_getX" );
        foo.called = "";
        foo.setX("x");
        assert.areEqual( foo.called, "_setX_setX" );
        assert.areEqual( foo.getX(), "x" );
        assert.areSame( foo, c.get("b") );
        assert.areEqual( foo.getX(), c.get("b").getX() );

        c.destroy();
        assert.areEqual( foo.destroy, false );

        var foo = c.get( "c" );
        assert.areEqual( foo.called, undefined );
        assert.areEqual( foo.name, "foo" );
        assert.areEqual( foo.init, true );
        assert.areEqual( foo.destroy, undefined  );
        assert.areEqual( foo.getX(), "x" );
        assert.areEqual( foo.called, "_getX_getX" );
        foo.called = "";
        foo.setX("y");
        assert.areEqual( foo.called, "_setX" );
        assert.areEqual( foo.getX(), "y" );
        assert.areNotSame( foo, c.get("c") );

        c.destroy();
        assert.areEqual( foo.destroy, undefined );
    },

    /**
     * 2重登録 / container.Container#gets() のテスト
     */
    testGets: function() {

        var assert = YAHOO.util.Assert;
        var c = new container.Container( function( binder ){
            binder.bind( animal.Kitten ).to( "kittens" ).inject( { name:"mii" }  );
            binder.bind( animal.Kitten ).to( "kittens" ).inject( { name:"tora" }  );
            binder.bind( animal.Kitten ).to( "kittens" ).inject( { name:"shiro" }  );
            binder.bind( animal.Kitten ).to( "kittens" ).inject( { name:"kuro" }  );
        } );
        var kittens = c.gets( "kittens" );
        assert.areEqual( kittens.length, 4 );
        assert.areEqual( kittens[0].getName(), "mii" );
        assert.areEqual( kittens[1].getName(), "tora" );
        assert.areEqual( kittens[2].getName(), "shiro" );
        assert.areEqual( kittens[3].getName(), "kuro" );

        // getでは最初に登録したものが返される。
        var mii = c.get( "kittens" );
        assert.areEqual( mii.getName(), "mii" );
        assert.areEqual( kittens[0], mii );
    },

    /**
     * container.Container#destroy() のテスト
     */
    testDestroy: function() {

       var assert = YAHOO.util.Assert;
        var c = new container.Container( function( binder ){
            binder.bind( animal.Kitten ).to( "mii" ).inject( { name:"mii" } ).destroy( function( obj, container ){
                assert.areSame( c, container );
                obj.destroy();
            } );
            binder.bind( animal.Kitten ).to( "tora" ).inject( { name:"tora" } ).destroy( function( obj, container ){
                assert.areSame( c, container );
                obj.destroy();
            } ).scope( container.Scope.Singleton );
            binder.bind( animal.Kitten ).to( "shiro" ).inject( { name:"shiro" } ).destroy( function( obj, container ){
                assert.areSame( c, container );
                obj.destroy();
            } ).scope( container.Scope.Prototype );
        } );
        var mii = c.get("mii");
        assert.areEqual( mii.destroyed, false);

        var tora = c.get("tora");
        assert.areEqual( tora.destroyed, false);

        var shiro = c.get("shiro");
        assert.areEqual( shiro.destroyed, false);

        c.destroy();
        assert.areEqual( mii.destroyed, true);
        assert.areEqual( tora.destroyed, true);
        assert.areEqual( shiro.destroyed, false);

        assert.areNotSame( mii, c.get("mii")  );
    },

    /**
     * ネームスペースのテスト
     */
    testNamespace: function() {

       var assert = YAHOO.util.Assert;
        var c = new container.Container( function( binder ){
            binder.bind( animal.Kitten ).to( "a" ).inject( { name:"mii" } );
            binder.ns( "x", function( binderX ) {
                binderX.bind( animal.Kitten ).to( "a" ).inject( { name:"tora" } );
                binderX.ns( "y", function( binderXY ) {
                    binderXY.bind( animal.Kitten ).to( "a" ).inject( { name: "shiro" } );
                });
            } );
        } );
        var kitten = c.get("a");
        assert.areEqual( kitten.getName(), "mii");

        kitten = c.get("x.a");
        assert.areEqual( kitten.getName(), "tora");

        kitten = c.get("x.y.a");
        assert.areEqual( kitten.getName(), "shiro");
    },

    /**
     * コンポーネント定義の列挙のテスト。
     */
    testEachComponentDefs: function() {

       var assert = YAHOO.util.Assert;
        var c = new container.Container( function( binder ){
            binder.bind( animal.Kitten ).to( "a" ).inject( { name:"mii" } );
            binder.bind( animal.Kitten ).to( "a" ).inject( { name:"mii" } );
            binder.bind( animal.Kitten ).to( "b" ).inject( { name:"mii" } );
            binder.bind( animal.Kitten ).to( "c" ).inject( { name:"mii" } );
        } );

        var list = {};
        c.eachComponentDef( function( defs ) {
            var name = defs[container.Annotation.Container][container.Annotation.Name];
            assert.isTrue( name == "a" || name == "b" || name == "c" );
            if ( list[name] ) {
              list[name]++;
            } else {
              list[name] = 1;
            }
        } );
        assert.areEqual( list["a"], 2);
        assert.areEqual( list["b"], 1);
        assert.areEqual( list["c"], 1);
    }
});

// テスト用クラス
if ( typeof animal == "undefined" ) { animal = {}; }


animal.Animal = function() {
  this.initialized = false;
  this.destroyed = false;
}
animal.Animal.prototype = {
    init: function()    { this.initialized = true; },
    destroy: function() { this.destroyed = true; },
    getName: function() { return this.name; },
    setName: function(name) { this.name = name; }
}

animal.Kitten = function() {}
animal.Kitten.prototype = new animal.Animal();
animal.Kitten.prototype.meow = function() {
    return "meow";
}

animal.Tiger = function() {}
animal.Tiger.prototype = new animal.Animal();
animal.Tiger.prototype.bite = function() {
    return "gabu";
}
